﻿
#include <BOpenGL>
#include <BPainter>
#include <BSystem>
#include <BReadWrite>
#include <BSonDocument>
#include "member_BWidget.h"
#include "member_BGlyph.h"
#include "member_BImage.h"
#include "member_BTexture.h"

#ifdef linux

#include <X11/cursorfont.h>

#endif

using namespace BWE;

extern BArray<GLuint>		UnusedTextureObjects;
extern BArray<GLuint>		UnusedFrameBufferObjects;
extern BArray<GLuint>		UnusedRenderBufferObjects;

member_BGlyph::member_BGlyph(BGlyph* pixmap)
{
	boss = pixmap;
	dirty = true;
	resized = true;
	fbo = 0;
	rbo = 0;
	msfbo = 0;
	msrbo = 0;
	mstbo = 0;
}
member_BGlyph::~member_BGlyph()
{

}

bool member_BGlyph::check() const
{
	GLenum state = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	switch (state)
	{
	case GL_FRAMEBUFFER_COMPLETE:
		return true;
	case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
		puts("FBO Initialization Failed : incomplete attachment");
		return false;
	case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
		puts("FBO Initialization Failed : missing attachment");
		return false;
	case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
		puts("FBO Initialization Failed : draw buffer is invalid");
		return false;
	case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
		puts("FBO Initialization Failed : read buffer is invalid");
		return false;
	case GL_FRAMEBUFFER_UNSUPPORTED:
		puts("FBO Initialization Failed : this is not supported");
		return false;
	}

	return 0 != glIsFramebuffer(fbo);
}
void member_BGlyph::frame()
{
	BSize size = boss->size();
	if (size.empty())
		return;
	GLuint mode = 0;
	Format format = boss->format();
	if (format == Format_RGBA)
		mode = GL_RGBA;
	if (format == Format_RGB)
		mode = GL_RGB;
	if (format == Format_Gray_Alpha)
		mode = GL_LUMINANCE_ALPHA;
	if (format == Format_Gray)
		mode = GL_LUMINANCE;
	if (format == Format_Alpha)
		mode = GL_ALPHA;
	if (fbo == 0 || rbo == 0)
		resized = true;
	if (resized)
	{
		GLint backup_frame_buffer;
		glGetIntegerv(GL_FRAMEBUFFER_BINDING, &backup_frame_buffer);
		if (!msfbo)
		{
			glGenFramebuffers(1, &msfbo);
			glGenRenderbuffers(1, &msrbo);
			glGenTextures(1, &mstbo);
		}
		if (msfbo)
		{
			glBindFramebuffer(GL_FRAMEBUFFER, msfbo);

			glBindRenderbuffer(GL_RENDERBUFFER, msrbo);
			glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, size.width(), size.height());
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msrbo);

			glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mstbo);
			glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, mode, size.width(), size.height(), GL_TRUE);
			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT7, GL_TEXTURE_2D_MULTISAMPLE, mstbo, 0);

			if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
				return;
		}
		if (!fbo)
		{
			glGenFramebuffers(1, &fbo);
			glGenRenderbuffers(1, &rbo);
		}
		if (fbo)
		{
			glBindFramebuffer(GL_FRAMEBUFFER, fbo);

			glBindRenderbuffer(GL_RENDERBUFFER, rbo);
			glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.width(), size.height());
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

			glBindTexture(GL_TEXTURE_2D, texture_member(boss)->tbo);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexImage2D(GL_TEXTURE_2D, 0, mode, size.width(), size.height(), 0, mode, GL_UNSIGNED_BYTE, 0);
			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT6, GL_TEXTURE_2D, texture_member(boss)->tbo, 0);

			if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
				return;
		}
		glBindFramebuffer(GL_FRAMEBUFFER, backup_frame_buffer);
		resized = false;
	}
	if (fbo)
	{
		state.save();

		if (msfbo > 0)
		{
			glBindFramebuffer(GL_FRAMEBUFFER, msfbo);
			glDrawBuffer(GL_COLOR_ATTACHMENT7);
			glReadBuffer(GL_COLOR_ATTACHMENT7);
		}
		else
		{
			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
			glDrawBuffer(GL_COLOR_ATTACHMENT6);
			glReadBuffer(GL_COLOR_ATTACHMENT6);
		}

		glClearColor(color.r() / 255.0f, color.g() / 255.0f, color.b() / 255.0f, color.a() / 255.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glClearDepth(1);
		glClear(GL_DEPTH_BUFFER_BIT);

		glClearStencil(0);
		glClear(GL_STENCIL_BUFFER_BIT);

		glDisable(GL_LIGHTING);
		glDisable(GL_DEPTH_TEST);
		glDisable(GL_CULL_FACE);

		glEnable(GL_MULTISAMPLE);
		glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);

		glEnable(GL_BLEND);
		glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
		glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);

		glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
		glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
		glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);

		glViewport(0, 0, size.width(), size.height());

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0, size.width(), size.height(), 0, -1, 1);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		boss->paint();

		if (msfbo)
		{
			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
			glDrawBuffer(GL_COLOR_ATTACHMENT6);
			glReadBuffer(GL_COLOR_ATTACHMENT6);
			glBindFramebuffer(GL_READ_FRAMEBUFFER, msfbo);
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
			glBlitFramebuffer(0, 0, size.width(), size.height(), 0, 0, size.width(), size.height(), GL_COLOR_BUFFER_BIT, GL_LINEAR);
			glBindFramebuffer(GL_FRAMEBUFFER, fbo);
		}

		boss->capture((BImage*)boss);
		//for (int r = 0; r < boss->height(); r++)
		//{
		//	BString text;
		//	int* colors = (int*)boss->bytes(r);
		//	for (int c = 0; c < boss->width(); c++)
		//	{
		//		text << colors[c] << ',';
		//	}
		//	puts(text.cstr());
		//}
		boss->reverse(Orientation_Vertical);
		boss->emit(Signal_Framed);
		state.open();
	}
}

void member_BGlyph::refreshStyle(BXmlNode* node)
{
	const BString& styleText = node->attrib("style");
	if (styleText.size())
	{
		IHtmlStyle* style = node->property("style");
		if (!style)
		{
			style = new IHtmlStyle();
			node->setProperty("style", style);
		}
		const BString& idStr = node->attrib("id");
		styleReader.read(styleText, style);
	}
	for (int i = 0; i < node->childCount(); i++)
	{
		BXmlNode* child = node->child(i);
		refreshStyle(child);
	}
}
void member_BGlyph::collectDefs(BXmlNode* node)
{
	if (node->name() == "defs")
	{
		for (int i = 0; i < node->childCount(); i++)
		{
			defNodes.append(node->child(i));
		}
	}
	for (int i = 0; i < node->childCount(); i++)
	{
		collectDefs(node->child(i));
	}
}
BXmlNode* member_BGlyph::findDefNode(const BString& idstr)
{
	for (int i = 0; i < defNodes.size(); i++)
	{
		BXmlNode* node = defNodes[i];
		if (node->attrib("id") == idstr)
			return node;
	}
	return 0;
}

BColor member_BGlyph::stringToColor(const BString& text)
{
	BColor color;
	if (text.empty() || text == "none")
	{
		return color;
	}
	if (text.beginWith('#'))
	{
		const char* cstr = text.cstr() + 1;
		int size = text.size() - 1;
		if (size == 3)
		{
			BByte r = bHexValue(cstr[0]);
			BByte g = bHexValue(cstr[1]);
			BByte b = bHexValue(cstr[2]);
			color.r() = r * 16 + r;
			color.g() = g * 16 + g;
			color.b() = b * 16 + b;
			color.a() = 255;
		}
		else if (size == 6)
		{
			color.r() = bHexToInt(cstr + 0, 2);
			color.g() = bHexToInt(cstr + 2, 2);
			color.b() = bHexToInt(cstr + 4, 2);
			color.a() = 255;
		}
		else if (size == 8)
		{
			color.r() = bHexToInt(cstr + 0, 2);
			color.g() = bHexToInt(cstr + 2, 2);
			color.b() = bHexToInt(cstr + 4, 2);
			color.a() = bHexToInt(cstr + 6, 2);
		}
		return color;
	}
	if (text.beginWith("rgb("))
	{
		int begin = text.find('(');
		int end = text.find(')');
		BString temp(text, begin + 1, end);
		BStringArray words;
		temp.split(words, ',');
		if (words.size() == 3)
		{
			color.r() = BByte(words[0].toInt());
			color.g() = BByte(words[1].toInt());
			color.b() = BByte(words[2].toInt());
			color.a() = 255;
		}
		return color;
	}
	if (text.beginWith("rgba("))
	{
		int begin = text.find('(');
		int end = text.find(')');
		BString temp(text, begin + 1, end);
		BStringArray words;
		temp.split(words, ',');
		if (words.size() == 4)
		{
			color.r() = BByte(words[0].toInt());
			color.g() = BByte(words[1].toInt());
			color.b() = BByte(words[2].toInt());
			color.a() = BByte(words[3].toFloat() * 255);
		}
		return color;
	}
	BString temp = text;
	temp.lower();
	color = BSystem::NamedColor(temp);
	return color;
}
Format member_BGlyph::stringToFormat(const BString& str)
{
	if (str == "rgb")
		return Format_RGB;
	if (str == "rgba")
		return Format_RGBA;
	if (str == "gray")
		return Format_Gray;
	if (str == "gray-alpha")
		return Format_Gray_Alpha;
	if (str == "alpha")
		return Format_Alpha;
	return Format_RGBA;
}

bool member_BGlyph::nextSegment(const BString& text, BString& segment, int& pos)
{
	static BString codes = "AaMmLlCcSsVvHhQqTtZz";
	segment.reset();
	while (pos < text.size())
	{
		char code = text[pos];
		if (segment.size())
		{
			if (codes.contain(code))
				return true;
		}
		segment.append(code);
		pos++;
	}
	return segment.size();
}
bool member_BGlyph::splitSegment(BString& segment, BStringArray& words)
{
	segment.trimmed();
	segment.simplify();
	if (segment.empty())
		return false;
	segment.replace(", ", ',');
	segment.replace(' ', ',');
	for (int i = 0; i < segment.size(); i++)
	{
		if (segment[i] == '-' && ::isdigit(segment(i - 1)))
		{
			segment.insert(i, ',');
			continue;
		}
		if (segment[i] == '.')
		{
			int back = i - 1;
			while (back >= 0)
			{
				if (segment[back] == '.')
					break;
				if (segment[back] == ',')
					back = 0;
				back--;
			}
			if (back >= 0)
				segment.insert(i, ',');
			continue;
		}
	}
	if (!segment.split(words, ','))
		return false;
	return true;
}

void member_BGlyph::buildArcPoints(BPointf begin, float rx, float ry, float xRot, int arcFlag, int sweepFlag, BPointf end)
{
	points.reset();
	int numPoints = 50;

	// 转换为弧度
	float phi = xRot * PIf / 180;
	float cosPhi = std::cos(phi);
	float sinPhi = std::sin(phi);

	// 将端点转换到非旋转坐标系
	float midX = (begin.x() - end.x()) / 2;
	float midY = (begin.y() - end.y()) / 2;

	float x1p = cosPhi * midX + sinPhi * midY;
	float y1p = -sinPhi * midX + cosPhi * midY;

	// 确保半径足够大
	float lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
	if (lambda > 1)
	{
		float sqrtLambda = std::sqrt(lambda);
		rx *= sqrtLambda;
		ry *= sqrtLambda;
	}

	// 计算中心点参数
	float sign = (arcFlag == sweepFlag) ? -1.0f : 1.0f;
	float divisor = (rx * rx * y1p * y1p) + (ry * ry * x1p * x1p);

	if (divisor == 0)// 直线情况 - 直接插值
	{
		for (int i = 1; i <= numPoints; ++i)
		{
			float t = (float)i / numPoints;
			points.append(BPointf(
				begin.x() + t * (end.x() - begin.x()),
				begin.y() + t * (end.y() - begin.y())
			));
		}
		return;
	}

	float coef = sign * std::sqrt(std::abs(
		(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p) / divisor
	));

	float cxp = coef * (rx * y1p / ry);
	float cyp = coef * (-ry * x1p / rx);

	// 计算中心点
	float cx = cosPhi * cxp - sinPhi * cyp + (begin.x() + end.x()) / 2;
	float cy = sinPhi * cxp + cosPhi * cyp + (begin.y() + end.y()) / 2;

	// 计算起始和结束角度
	float startAngle = std::atan2((y1p - cyp) / ry, (x1p - cxp) / rx);
	float endAngle = std::atan2((-y1p - cyp) / ry, (-x1p - cxp) / rx);

	// 调整角度范围
	float deltaAngle = endAngle - startAngle;

	if (sweepFlag == 0 && deltaAngle > 0)
	{
		deltaAngle -= 2 * PIf;
	}
	else if (sweepFlag == 1 && deltaAngle < 0)
	{
		deltaAngle += 2 * PIf;
	}

	for (int i = 1; i <= numPoints; ++i)
	{
		float theta = startAngle + (deltaAngle * i) / numPoints;
		float x = cx + rx * cosPhi * std::cos(theta) - ry * sinPhi * std::sin(theta);
		float y = cy + rx * sinPhi * std::cos(theta) + ry * cosPhi * std::sin(theta);
		points.append(BPointf(x, y));
	}
}
void member_BGlyph::buildBezierPoints(const BPointf& begin, const BPointf& ctrl1, const BPointf& ctrl2, const BPointf& end, float scaling)
{
	points.reset();
	int numPoints = int((begin - end).length() * scaling) / 2;
	numPoints = bClamp(numPoints, 3, 64);
	for (int i = 1; i <= numPoints; ++i)
	{
		float t = float(i) / numPoints;
		float u = 1 - t;

		float x = u * u * u * begin.x() +
			3 * u * u * t * ctrl1.x() +
			3 * u * t * t * ctrl2.x() +
			t * t * t * end.x();

		float y = u * u * u * begin.y() + 
			3 * u * u * t * ctrl1.y() +
			3 * u * t * t * ctrl2.y() +
			t * t * t * end.y();

		points.append(BPointf(x, y));
	}
}
void member_BGlyph::buildSmoothBezierPoints(const BPointf& begin, const BPointf& ctrl2, const BPointf& end, float scaling)
{
	points.reset();
	int numPoints = int((begin - end).length() * scaling) / 2;
	numPoints = bClamp(numPoints, 3, 64);

	BPointf ctrl1;
	ctrl1.x() = 2 * begin.x() - prevCtrl2.x();
	ctrl1.y() = 2 * begin.y() - prevCtrl2.y();

	for (int i = 1; i <= numPoints; ++i)
	{
		float t = float(i) / numPoints;
		float u = 1 - t;

		float x = u * u * u * begin.x() +
			3 * u * u * t * ctrl1.x() +
			3 * u * t * t * ctrl2.x() +
			t * t * t * end.x();

		float y = u * u * u * begin.y() +
			3 * u * u * t * ctrl1.y() +
			3 * u * t * t * ctrl2.y() +
			t * t * t * end.y();

		points.append(BPointf(x, y));
	}
}
void member_BGlyph::buildSmoothBezierPoints(const BPointf& begin, const BPointf& end, float scaling)
{
	points.reset();
	int numPoints = int((begin - end).length() * scaling) / 2;
	numPoints = bClamp(numPoints, 3, 64);

	BPointf ctrl1;
	ctrl1.x() = 2 * begin.x() - prevCtrl2.x();
	ctrl1.y() = 2 * begin.y() - prevCtrl2.y();

	for (int i = 1; i <= numPoints; ++i)
	{
		float t = (float)i / numPoints;
		float u = 1 - t;
		float x = u * u * begin.x() + 2 * u * t * ctrl1.x() + t * t * end.x();
		float y = u * u * begin.y() + 2 * u * t * ctrl1.y() + t * t * end.y();
		points.append(BPointf(x, y));
	}

	prevCtrl2 = ctrl1;
}
void member_BGlyph::buildQuadraticBezier(const BPointf& begin, const BPointf& ctrl, const BPointf& end, float scaling)
{
	points.reset();
	int numPoints = int((begin - end).length() * scaling) / 2;
	numPoints = bClamp(numPoints, 3, 64);
	for (int i = 1; i <= numPoints; ++i)
	{
		float t = float(i) / numPoints;
		float one_minus_t = 1.0f - t;
		float x = one_minus_t * one_minus_t * begin.x() + 2 * one_minus_t * t * ctrl.x() + t * t * end.x();
		float y = one_minus_t * one_minus_t * begin.y() + 2 * one_minus_t * t * ctrl.y() + t * t * end.y();
		points.append(BPointf(x, y));
	}
}

const BString& member_BGlyph::findAttribValue(BXmlNode* node, const char* name)
{
	const BString& value = node->attrib(name);
	if (value.empty())
	{
		if (BXmlNode* upper = node->property("upper"))
		{
			return findAttribValue(upper, name);
		}
		if (BXmlNode* parent = node->parent())
		{
			return findAttribValue(parent, name);
		}
	}
	return value;
}
const BString& member_BGlyph::findStyleValue(BXmlNode* node, const char* name)
{
	if (const IHtmlStyle* style = node->property("style"))
	{
		const BString& valueStr = style->value(name);
		if (valueStr.size())
			return valueStr;
	}
	if (BXmlNode* upper = node->property("upper"))
	{
		return findStyleValue(upper, name);
	}
	if (BXmlNode* parent = node->parent())
	{
		return findStyleValue(parent, name);
	}
	return node->property("");
}
const BString& member_BGlyph::findProperty(BXmlNode* node, const char* name)
{
	const BString& styleValue = findStyleValue(node, name);
	if (styleValue.size())
		return styleValue;
	const BString& attribValue = findAttribValue(node, name);
	if (attribValue.size())
		return attribValue;
	return styleReader.message();
}
float member_BGlyph::findStrokeWidth(BXmlNode* node)
{
	float strokeWidth = 1;
	const BString& strokeWidthStr = findProperty(node, "stroke-width");
	if (strokeWidthStr.size())
		strokeWidth = strokeWidthStr.toFloat();
	return strokeWidth;
}
BColor member_BGlyph::findStrokeColor(BXmlNode* node)
{
	BColor strokeColor;
	const BString& strokeStr = findProperty(node, "stroke");
	if (strokeStr.size())
		strokeColor = stringToColor(strokeStr);
	const BString& opacityStr = findProperty(node, "opacity");
	if (opacityStr.size())
	{
		float opacity = opacityStr.toFloat();
		strokeColor.a() = BByte(opacity * 255);
	}
	return strokeColor;
}
BColor member_BGlyph::findFillColor(BXmlNode* node)
{
	const BString& idStr = node->attrib("id");
	BColor fillColor(0);
	const BString& fillStr = findProperty(node, "fill");
	if (fillStr.size())
		fillColor = stringToColor(fillStr);
	const BString& opacityStr = findProperty(node, "opacity");
	if (opacityStr.size())
	{
		float opacity = opacityStr.toFloat();
		fillColor.a() = BByte(opacity * 255);
	}
	return fillColor;
}

void member_BGlyph::paintNode(BPainter& painter, BXmlNode* node)
{
	const BString& name = node->name();
	if (name == "defs")
		return;
	BUInt mask = 0;
	const BString& maskStr = node->attrib("mask");
	if (maskStr.size())
	{
		const BSize& size = boss->size();
		int id_begin = maskStr.find('#') + 1;
		int id_end = maskStr.find(')', id_begin);
		BString idstr = maskStr.substr(id_begin, id_end);
		if (BXmlNode* defNode = findDefNode(idstr))
		{
			if (msfbo)
			{
				glBindFramebuffer(GL_FRAMEBUFFER, fbo);
				glDrawBuffer(GL_COLOR_ATTACHMENT6);
				glReadBuffer(GL_COLOR_ATTACHMENT6);
			}

			glGenTextures(1, &mask);
			glBindTexture(GL_TEXTURE_2D, mask);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT6, GL_TEXTURE_2D, mask, 0);
			maskTbos.push(mask);

			defNode->setProperty("upper", node);
			paintNode(painter, defNode);
			defNode->setProperty("upper");
			glDisable(GL_DEPTH_TEST);

			//BImage image(size, Format_RGBA);
			//glBindTexture(GL_TEXTURE_2D, mask);
			//glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.bytes());
			//glReadPixels(0, 0, size.width(), size.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.bytes());
			//WriteFile("./test1.png", &image);
			//glBindTexture(GL_TEXTURE_2D, 0);

			glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT6, GL_TEXTURE_2D, texture_member(boss)->tbo, 0);
			if (msfbo)
			{
				glBindFramebuffer(GL_FRAMEBUFFER, msfbo);
				glDrawBuffer(GL_COLOR_ATTACHMENT7);
				glReadBuffer(GL_COLOR_ATTACHMENT7);
			}

			if (maskProgrram.empty())
			{
				static const char* vertShaderText = R"(#version 120
				varying vec2 vertex;
				void main()
				{
					gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
					vertex = gl_Position.xy;
				}
				)";
				static const char* fragShaderText = R"(#version 120
				uniform vec2 sampleSize;
				uniform sampler2D maskTextures;
				uniform int maskCount;
				void main()
				{
					vec2 textCoord = vec2(gl_FragCoord.x / sampleSize.x, gl_FragCoord.y / sampleSize.y);
					vec4 color = gl_Color;
					vec4 maskColor = texture2D(maskTextures, textCoord);
					float maskAlpha = dot(maskColor.rgb, vec3(0.299, 0.587, 0.114));
					color.a = maskAlpha;
					gl_FragColor = color;
				}
				)";
				maskProgrram = new BProgram();
				BShader* vertShader = new BShader(BShader::Type_Vertex, vertShaderText);
				BShader* fragShader = new BShader(BShader::Type_Fragment, fragShaderText);
				maskProgrram->addShader(vertShader);
				maskProgrram->addShader(fragShader);
				BUniform* sampleSizeUniform = new BUniform("sampleSize", BUniform::Type_Vec2f);
				BUniform* masksUniform = new BUniform("maskTextures", BUniform::Type_Int);
				BUniform* maskCountUniform = new BUniform("maskCount", BUniform::Type_Int);
				maskProgrram->addUniform(sampleSizeUniform);
				maskProgrram->addUniform(masksUniform);
				maskProgrram->addUniform(maskCountUniform);
			}
			BUniform* sampleSizeUniform = maskProgrram->uniform("sampleSize");
			sampleSizeUniform->set(BPointf((float)size.width(), (float)size.height()));
			BUniform* maskCountUniform = maskProgrram->uniform("maskCount");
			maskCountUniform->set(maskTbos.size());
			BUniform* masksUniform = maskProgrram->uniform("maskTextures");
			masksUniform->set(0);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, mask);
			painter.setProgram(maskProgrram);
		}
	}
	const BString& transform = node->attrib("transform");
	if (transform.size())
	{
		painter.pushMatrix();
		BString word;
		BStringArray words;
		int pos = 0;
		while (pos < transform.size())
		{
			pos = transform.word(word, pos);
			if (word == "translate")
			{
				int tr_pos = pos + 1;
				pos = transform.find(')', tr_pos);
				if (tr_pos < pos)
				{
					BString segment = transform.substr(tr_pos, pos);
					splitSegment(segment, words);
					float tr_x = words(0).toFloat();
					float tr_y = words(1).toFloat();
					painter.translate(tr_x, tr_y);
				}
				pos++;
			}
			if (word == "rotate")
			{
				int rot_pos = pos + 1;
				pos = transform.find(')', rot_pos);
				if (rot_pos < pos)
				{
					BString segment = transform.substr(rot_pos, pos);
					splitSegment(segment, words);
					float rot = words(0).toFloat();
					float cx = words(1).toFloat();
					float cy = words(2).toFloat();
					if (words.size() == 1)
					{
						painter.rotate(rot);
					}
					else if (words.size() == 3)
					{
						painter.translate(cx, cy);
						painter.rotate(rot);
						painter.translate(-cx, -cy);
					}
				}
				pos++;
			}
			if (word == "matrix")
			{
				int mat_pos = pos + 1;
				pos = transform.find(')', mat_pos);
				if (mat_pos < pos)
				{
					BString segment = transform.substr(mat_pos, pos);
					splitSegment(segment, words);
					float a = words(0).toFloat();
					float b = words(1).toFloat();
					float c = words(2).toFloat();
					float d = words(3).toFloat();
					float e = words(4).toFloat();
					float f = words(5).toFloat();
					BMatrixf matrix;
					matrix.setRow(0, a, b, 0);
					matrix.setRow(1, c, d, 0);
					matrix.setRow(2, 0, 0, 1);
					matrix.setRow(3, e, f, 0);
					BOpenGL::glMultMatrix(matrix);
				}
				pos++;
			}
		}
	}

	if (name == "use")
		paintUse(painter, node);
	else if (name == "line")
		paintLine(painter, node);
	else if (name == "rect")
		paintRect(painter, node);
	else if (name == "circle")
		paintCircle(painter, node);
	else if (name == "polyline")
		paintPolyline(painter, node);
	else if (name == "polygon")
		paintPolygon(painter, node);
	else if (name == "path")
		paintPath(painter, node);

	for (int i = 0; i < node->childCount(); i++)
	{
		paintNode(painter, node->child(i));
	}
	if (transform.size())
	{
		painter.popMatrix();
	}
	if (maskStr.size() && mask)
	{
		maskTbos -= mask;
		BUniform* masksUniform = maskProgrram->uniform("masks");
		for (int i = 0; i < maskTbos.size(); i++)
			masksUniform->set(i, maskTbos[i]);
		painter.setProgram(0);
		glDeleteTextures(1, &mask);
	}
}
void member_BGlyph::paintRect(BPainter& painter, BXmlNode* node)
{
	BPointf pos;
	pos.x() = node->attrib("x").toFloat();
	pos.y() = node->attrib("y").toFloat();

	BSizef size;
	size.width() = node->attrib("width").toFloat();
	size.height() = node->attrib("height").toFloat();

	BColor fillColor = findFillColor(node);
	if (painter.setColor(fillColor))
	{
		painter.fillRect(pos, size);
	}
	float strokeWidth = findStrokeWidth(node);
	BColor strokeColor = findStrokeColor(node);
	if (strokeWidth > 0 && painter.setColor(strokeColor))
	{
		painter.setLineWidth(strokeWidth * (float)painter.scaling());
		painter.drawRect(pos, size);
	}
}
void member_BGlyph::paintCircle(BPainter& painter, BXmlNode* node)
{
	BPointf center;
	center.x() = node->attrib("cx").toFloat();
	center.y() = node->attrib("cy").toFloat();
	float radius = node->attrib("radius").toFloat();

	BColor fillColor = findFillColor(node);
	if (painter.setColor(fillColor))
	{
		painter.fillCircle(center, radius);
	}

	float strokeWidth = findStrokeWidth(node);
	BColor strokeColor = findStrokeColor(node);

	painter.setLineWidth(strokeWidth * (float)painter.scaling());
	if (strokeWidth > 0 && painter.setColor(strokeColor))
	{
		painter.setLineWidth(strokeWidth * (float)painter.scaling());
		painter.drawCircle(center, radius);
	}
}
void member_BGlyph::paintLine(BPainter& painter, BXmlNode* node)
{
	BPointf begin;
	begin.x() = node->attrib("x1").toFloat();
	begin.y() = node->attrib("y1").toFloat();

	BPointf end;
	end.x() = node->attrib("x2").toFloat();
	end.y() = node->attrib("y2").toFloat();

	float strokeWidth = findStrokeWidth(node);
	BColor strokeColor = findStrokeColor(node);
	if (strokeWidth > 0 && painter.setColor(strokeColor))
	{
		painter.setLineWidth(strokeWidth * (float)painter.scaling());
		painter.drawLine(begin, end);
	}
}
void member_BGlyph::paintPolyline(BPainter& painter, BXmlNode* node)
{
	float strokeWidth = findStrokeWidth(node);
	BColor strokeColor = findStrokeColor(node);
	BPointArray	points;
	if (strokeWidth > 0 && painter.setColor(strokeColor))
	{
		painter.setLineWidth(strokeWidth * (float)painter.scaling());
		painter.drawLineStrip(points);
	}
}
void member_BGlyph::paintPolygon(BPainter& painter, BXmlNode* node)
{
	const BString& pointsText = node->attrib("points");
	if (pointsText.empty())
		return;

	float strokeWidth = findStrokeWidth(node);
	BColor strokeColor = findStrokeColor(node);
	BColor fillColor = findFillColor(node);

	polygon.reset();
	BStringArray words;
	BString segment;
	int pos = 0;
	while (pos < pointsText.size())
	{
		int split = pointsText.find(' ', pos);
		pointsText.substr(segment, pos, split);
		if (2 == segment.split(words, ','))
		{
			float x = words[0].toFloat();
			float y = words[1].toFloat();
			polygon.append(BPointf(x, y));
		}
		pos = split + 1;
	}
	if (polygon.area() < 0)
		polygon.reverse();

	if (painter.setColor(strokeColor))
	{
		painter.setLineWidth(strokeWidth * (float)painter.scaling());
		painter.drawLineLoop(polygon.points());
	}
	if (painter.setColor(fillColor))
	{
		const BIntArray& indices = polygon.trigIndices();
		for (int i = 0; i < indices.size(); i += 3)
		{
			int ia = indices[i];
			int ib = indices[i + 1];
			int ic = indices[i + 2];
			painter.fillTriangle(polygon[ia], polygon[ib], polygon[ic]);
		}
	}
	//if (painter.setColor(0, 0, 200))
	//{
	//	const BIntArray& indices = polygon.trigIndices();
	//	for (int i = 0; i < indices.size(); i += 3)
	//	{
	//		int ia = indices[i];
	//		int ib = indices[i + 1];
	//		int ic = indices[i + 2];
	//		painter.drawTriangle(polygon[ia], polygon[ib], polygon[ic]);
	//	}
	//}
	//painter.setColor(200, 0, 0);
	//painter.setLineWidth(3);
	//painter.drawLineStrip(polygon.points());
}
void member_BGlyph::paintPath(BPainter& painter, BXmlNode* node)
{
	const BString idStr = node->attrib("id");
	float strokeWidth = findStrokeWidth(node);
	BColor strokeColor = findStrokeColor(node);
	BColor fillColor = findFillColor(node);
	const BString& dataStr = node->attrib("d");
	if (dataStr.size())
	{
		BArray<BPolygon2f> polygons;
		polygon.reset();
		BStringArray words;
		BString segment;
		int level = 0;
		int pos = 0;
		while (char mode = nextSegment(dataStr, segment, pos))
		{
			char head = segment[0];
			segment.remove(0);
			segment.trimmed();
			if (segment.size() && !splitSegment(segment, words))
				continue;
			if (head == 'M')
			{
				polygon.reset();
				current.x() = words[0].toFloat();
				current.y() = words[1].toFloat();
				polygon.append(current);
				int i = 2;
				while (i < words.size())
				{
					current.x() = words[i++].toFloat();
					current.y() = words[i++].toFloat();
					polygon.append(current);
				}
				continue;
			}
			if (head == 'm')
			{
				polygon.reset();
				current.x() += words[0].toFloat();
				current.y() += words[1].toFloat();
				polygon.append(current);
				int i = 2;
				while (i < words.size())
				{
					current.x() += words[i++].toFloat();
					current.y() += words[i++].toFloat();
					polygon.append(current);
				}
				continue;
			}
			if (head == 'L')
			{
				BPointf next;
				int i = 0;
				while (i < words.size())
				{
					next.x() = words[i++].toFloat();
					next.y() = words[i++].toFloat();
					polygon.append(next);
				}
				continue;
			}
			if (head == 'l')
			{
				BPointf next = polygon.last();
				int i = 0;
				while (i < words.size())
				{
					next.x() += words[i++].toFloat();
					next.y() += words[i++].toFloat();
					polygon.append(next);
				}
				continue;
			}
			if (head == 'H')
			{
				BPointf next = polygon.last();
				next.x() = segment.toFloat();
				polygon.append(next);
				continue;
			}
			if (head == 'h')
			{
				BPointf next = polygon.last();
				next.x() += segment.toFloat();
				polygon.append(next);
				continue;
			}
			if (head == 'V')
			{
				BPointf next = polygon.last();
				next.y() = segment.toFloat();
				polygon.append(next);
				continue;
			}
			if (head == 'v')
			{
				BPointf next = polygon.last();
				next.y() += segment.toFloat();
				polygon.append(next);
				continue;
			}
			if (head == 'A')
			{
				int i = 0;
				while (i < words.size())
				{
					BPointf begin = polygon.last();
					float rx = words(i++).toFloat();
					float ry = words(i++).toFloat();
					float rotX = words(i++).toFloat();
					int arc_flag = words(i++).toInt();
					int sweep_flag = words(i++).toInt();
					BPointf end;
					end.x() = words(i++).toFloat();
					end.y() = words(i++).toFloat();
					buildArcPoints(begin, rx, ry, rotX, arc_flag, sweep_flag, end);
					polygon.append(points);
				}
				continue;
			}
			if (head == 'a')
			{
				int i = 0;
				while (i < words.size())
				{
					BPointf begin = polygon.last();
					float rx = words(i++).toFloat();
					float ry = words(i++).toFloat();
					float rotX = words(i++).toFloat();
					int arc_flag = words(i++).toInt();
					int sweep_flag = words(i++).toInt();
					BPointf end = begin;
					end.x() += words(i++).toFloat();
					end.y() += words(i++).toFloat();
					buildArcPoints(begin, rx, ry, rotX, arc_flag, sweep_flag, end);
					polygon.append(points);
				}
				continue;
			}
			if (head == 'C')
			{
				int i = 0;
				while (i < words.size())
				{
					BPointf begin = polygon.last();
					BPointf ctrl1;
					ctrl1.x() = words(i++).toFloat();
					ctrl1.y() = words(i++).toFloat();
					BPointf ctrl2;
					ctrl2.x() = words(i++).toFloat();
					ctrl2.y() = words(i++).toFloat();
					BPointf end;
					end.x() = words(i++).toFloat();
					end.y() = words(i++).toFloat();
					buildBezierPoints(begin, ctrl1, ctrl2, end, (float)painter.scaling());
					polygon.append(points);
					prevCtrl2 = ctrl2;
				}
				continue;
			}
			if (head == 'c')
			{
				int i = 0;
				while (i < words.size())
				{
					BPointf begin = polygon.last();
					BPointf ctrl1;
					ctrl1.x() = words(i++).toFloat();
					ctrl1.y() = words(i++).toFloat();
					ctrl1 += begin;
					BPointf ctrl2;
					ctrl2.x() = words(i++).toFloat();
					ctrl2.y() = words(i++).toFloat();
					ctrl2 += begin;
					BPointf end;
					end.x() = words(i++).toFloat();
					end.y() = words(i++).toFloat();
					end += begin;
					buildBezierPoints(begin, ctrl1, ctrl2, end, (float)painter.scaling());
					polygon.append(points);
					prevCtrl2 = ctrl2;
				}
				continue;
			}
			if (head == 'Q')
			{
				BPointf begin = polygon.last();
				BPointf ctrl;
				ctrl.x() = words[0].toFloat();
				ctrl.y() = words[1].toFloat();
				BPointf end;
				end.x() = words[2].toFloat();
				end.y() = words[3].toFloat();
				buildQuadraticBezier(begin, ctrl, end, (float)painter.scaling());
				polygon.append(points);
				prevCtrl2 = ctrl;
				continue;
			}
			if (head == 'q')
			{
				BPointf begin = polygon.last();
				BPointf ctrl;
				ctrl.x() = words[0].toFloat();
				ctrl.y() = words[1].toFloat();
				ctrl += begin;
				BPointf end;
				end.x() = words[2].toFloat();
				end.y() = words[3].toFloat();
				end += begin;
				buildQuadraticBezier(begin, ctrl, end, (float)painter.scaling());
				polygon.append(points);
				prevCtrl2 = ctrl;
				continue;
			}
			if (head == 'S')
			{
				int i = 0;
				while (i < words.size())
				{
					BPointf begin = polygon.last();
					BPointf ctrl2;
					ctrl2.x() = words(i++).toFloat();
					ctrl2.y() = words(i++).toFloat();
					BPointf end;
					end.x() = words(i++).toFloat();
					end.y() = words(i++).toFloat();
					buildSmoothBezierPoints(begin, ctrl2, end, (float)painter.scaling());
					polygon.append(points);
					prevCtrl2 = ctrl2;
				}
				continue;
			}
			if (head == 's')
			{
				int i = 0;
				while (i < words.size())
				{
					BPointf begin = polygon.last();
					BPointf ctrl2;
					ctrl2.x() = words(i++).toFloat();
					ctrl2.y() = words(i++).toFloat();
					ctrl2 += begin;
					BPointf end;
					end.x() = words(i++).toFloat();
					end.y() = words(i++).toFloat();
					end += begin;
					buildSmoothBezierPoints(begin, ctrl2, end, (float)painter.scaling());
					polygon.append(points);
					prevCtrl2 = ctrl2;
				}
				continue;
			}
			if (head == 'T')
			{
				int i = 0;
				while (i < words.size())
				{
					BPointf begin = polygon.last();
					BPointf end;
					end.x() = words[i++].toFloat();
					end.y() = words[i++].toFloat();
					buildSmoothBezierPoints(begin, end, (float)painter.scaling());
					polygon.append(points);
				}
				continue;
			}
			if (head == 't')
			{
				int i = 0;
				while (i < words.size())
				{
					BPointf begin = polygon.last();
					BPointf end;
					end.x() = words[i++].toFloat();
					end.y() = words[i++].toFloat();
					end += begin;
					buildSmoothBezierPoints(begin, end, (float)painter.scaling());
					polygon.append(points);
				}
				continue;
			}
			if (head == 'Z' || head == 'z')
			{
				points = polygon.points();
				while (points.size() > 2)
				{
					bool intersect = false;
					for (int i = 0; i < points.size() - 1; i++)
					{
						BLine2f line(points[i], points[i + 1]);
						BPointf phit;
						int end = i + 1;
						while (++end < points.size() - 1)
						{
							BLine2f after(points[end], points[end + 1]);
							if (intersect = line.intersect(after, phit))
								break;
						}
						if (intersect)
						{
							BPolygon2f& polygon = polygons.append();
							polygon.append(phit);
							polygon.append(points, i + 1, end - i);
							points.remove(i + 1, end - i);
							points.insert(i + 1, phit);
							break;
						}
					}
					//if (intersect)
					//	break;
					if (!intersect)
					{
						polygons.append().append(points);
						break;
					}
				}
				current = polygon.first();
				polygon.reset();
			}
		}
		for (int i = 0; i < polygons.size(); i++)
		{
			BPolygon2f& polygon = polygons[i];
			if (polygon.area() < 0)
				polygon.reverse();
		}
		if (polygons.size() > 1)
		{
			glEnable(GL_STENCIL_TEST);
			glStencilMask(0xFF);
			glClearStencil(0);
			glClear(GL_STENCIL_BUFFER_BIT);

			glDepthMask(GL_FALSE);
			glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
			glStencilFunc(GL_ALWAYS, 0, 0xFF);
			glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT);
			glStencilMask(0xFF);

			for (int i = 0; i < polygons.size(); i++)
			{
				BPolygon2f& polygon = polygons[i];
				const BIntArray& indices = polygon.trigIndices();
				for (int i = 0; i < indices.size(); i += 3)
				{
					int ia = indices[i];
					int ib = indices[i + 1];
					int ic = indices[i + 2];
					painter.fillTriangle(polygon[ia], polygon[ib], polygon[ic]);
				}
			}

			glDepthMask(GL_TRUE);
			glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
			glStencilFunc(GL_EQUAL, 0xFF, 0xFF);
			glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
			glStencilMask(0x00);
		}
		for (int i = 0; i < polygons.size(); i++)
		{
			BPolygon2f& polygon = polygons[i];
			if (painter.setColor(fillColor))
			{
				const BIntArray& indices = polygon.trigIndices();
				for (int i = 0; i < indices.size(); i += 3)
				{
					int ia = indices[i];
					int ib = indices[i + 1];
					int ic = indices[i + 2];
					painter.fillTriangle(polygon[ia], polygon[ib], polygon[ic]);
				}
			}
			if (painter.setColor(strokeColor))
			{
				painter.setLineWidth(strokeWidth);// *(float)painter.scaling());
				painter.setLineSmooth(true);
				painter.drawLineLoop(polygon.points());
			}
		}
		if (polygon.size() > 1)
		{
			if (painter.setColor(fillColor))
			{
				const BIntArray& indices = polygon.trigIndices();
				for (int i = 0; i < indices.size(); i += 3)
				{
					int ia = indices[i];
					int ib = indices[i + 1];
					int ic = indices[i + 2];
					painter.fillTriangle(polygon[ia], polygon[ib], polygon[ic]);
				}
			}
			if (painter.setColor(strokeColor))
			{
				painter.setLineWidth(strokeWidth);// *(float)painter.scaling());
				painter.setLineSmooth(true);
				painter.drawLineStrip(polygon.points());
			}
		}
		glDisable(GL_STENCIL_TEST);
	}
}
void member_BGlyph::paintUse(BPainter& painter, BXmlNode* node)
{
	BString idstr = node->attrib("xlink:href");
	if (idstr.beginWith('#'))
	{
		idstr.remove('#');
		if (BXmlNode* defNode = findDefNode(idstr))
		{
			defNode->setProperty("upper", node);
			paintNode(painter, defNode);
			defNode->setProperty("upper");
		}
	}
}
